﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Yxinmt.Entity;

namespace Yxinmt.Windows.Meeting
{
    public class UploadDownload
    {
        readonly int ChunkSize = 1024 * 1024 * 40;

        public bool IsUploading {  get; set; }
        public bool IsDownloading { get; set; }

        public long TotalMillSecnods { get; set; }

        Stopwatch _Stopwatch { get; set; }

        CancellationTokenSource _CancellationTokenSource { get; set; }

        public void Upload(Guid meetingId, List<Entity.UploadFile> files, Action<long> complete)
        {
            Task.Run(() =>
            {
                _CancellationTokenSource = new CancellationTokenSource();
                IsUploading = true;
                _Stopwatch = new Stopwatch();
                _Stopwatch.Start();
                int index = 0;
                foreach (var file in files)
                {
                    index++;
                    file.Index = index;
                    if (IsUploading) UploadFile(meetingId, file);
                    else break;
                }
                _Stopwatch.Stop();
                complete(_Stopwatch.ElapsedMilliseconds);
                _CancellationTokenSource = null;
            });
        }

        public static Action<Entity.FileInfo> DownloadProgress { get; set; }

        static System.Collections.Concurrent.ConcurrentQueue<Entity.FileInfo> _DownloadFileQueue { get; set; }

        static UploadDownload()
        {
            _DownloadFileQueue = new System.Collections.Concurrent.ConcurrentQueue<Entity.FileInfo>();
        }

        public static CancellationTokenSource  CancellationTokenSource { get; set; }
        public static Entity.FileInfo SelectFileInfo { get; set; }

        static CancellationTokenSource _StartDownloadCancellationTokenSource { get; set; }

        static void StartDownload()
        {
            if (_DownloadFileQueue.Count == 0) return;

            if (_StartDownloadCancellationTokenSource != null)
            {
                _StartDownloadCancellationTokenSource.Cancel();
                _StartDownloadCancellationTokenSource.Dispose();
                _StartDownloadCancellationTokenSource = null;

                Thread.Sleep(1000);
            }
            _StartDownloadCancellationTokenSource = new CancellationTokenSource();

            Task.Run(() =>
            {
                CancellationTokenSource = new CancellationTokenSource();

                while (CancellationTokenSource != null && _DownloadFileQueue.TryDequeue(out var file) && file != null)
                {
                    if (JudgeComplete(file)) continue;

                    SelectFileInfo = file;

                    DownloadFile(_SelectMeetingId, file, ServiceUrl, (p) =>
                    {
                        if (ProgressCallback2 != null) ProgressCallback2(p);
                        file.Percentage = p.Percentage;
                        if (p.Percentage == 100)
                        {
                            file.DownloadProgress = $"已缓存（{Utility.Common.GetFileSize(file.FileSize)}）";
                        }
                        else
                        {
                            string size1 = Utility.Common.GetFileSize(p.SentBytes);
                            string size2 = Utility.Common.GetFileSize(p.TotalBytes);
                            string message = $"正在下载中，已下载：{size1}/{size2}，下载进度：{p.Percentage}%";

                            file.DownloadProgress = $"缓存中{p.Percentage}%（{Utility.Common.GetFileSize(file.FileSize)}）";
                        }

                        DownloadProgress(file);
                    }, (filePath) =>
                    {
                        if (Complete != null) Complete(filePath);
                    });
                }

                CancellationTokenSource = null;
                SelectFileInfo = null;
            }, _StartDownloadCancellationTokenSource.Token);
        }

        static Guid _SelectMeetingId { get; set; }

        public static List<Entity.FileInfo> DownloadFileList(Guid meetingId, List<string> videoFormatList, List<Entity.FileInfo> fileList)
        {
            List<Entity.FileInfo> fileList2 = new List<Entity.FileInfo>();
            List<Entity.FileInfo> videoList = new List<Entity.FileInfo>();
            fileList.ForEach(file =>
            {
                file.FilePath = AppDomain.CurrentDomain.BaseDirectory + "res\\" + meetingId.ToString() + "\\" + file.FileMd5 + "\\" + file.FileName;
                if (JudgeComplete(file))
                {
                    file.DownloadProgress = $"已缓存（{Utility.Common.GetFileSize(file.FileSize)}）";
                    file.Percentage = 100;
                }
                else
                {
                    file.DownloadProgress = $"待缓存（{Utility.Common.GetFileSize(file.FileSize)}）";
                    file.Index = new Random().NextDouble() * 100000;
                    if (videoFormatList.Contains(file.FileType)) videoList.Add(file);
                    else fileList2.Add(file);
                }
            });

            if (_SelectMeetingId != meetingId)
            {
                if (_SelectMeetingId != Guid.Empty)
                {
                    cancelToken();
                    SelectFileInfo = null;
                }

                _SelectMeetingId = meetingId;
            }
            else return fileList;

            _DownloadFileQueue = new System.Collections.Concurrent.ConcurrentQueue<Entity.FileInfo>();

            fileList2 = fileList2.OrderBy(b => b.Index).ToList();
            fileList2.ForEach(file =>
            {
                _DownloadFileQueue.Enqueue(file);
            });

            videoList = videoList.OrderBy(b => b.Index).ToList();
            videoList.ForEach(file =>
            {
                _DownloadFileQueue.Enqueue(file);
            });

            StartDownload();

            return fileList;
        }

        public static string ServiceUrl { get; set; }

        public static Action<ProgressStatus> ProgressCallback2 { get; set; }
        public static Action<string> Complete { get; set; }

        public static bool JudgeComplete(Entity.FileInfo file)
        {
            if (File.Exists(file.FilePath))
            {
                System.IO.FileInfo fileInfo = new System.IO.FileInfo(file.FilePath);
                return file.FileSize == fileInfo.Length;
            }
            return false;
        }

        static void cancelToken()
        {
            if (CancellationTokenSource != null)
            {
                CancellationTokenSource.Cancel();
                CancellationTokenSource.Dispose();
                CancellationTokenSource = null;
            }
        }

        public static void DownloadFile(Guid meetingId, Entity.FileInfo file, Action<ProgressStatus> progressCallback, Action<string> complete)
        {
            try
            {
                _SelectMeetingId = meetingId;
                file.FilePath = AppDomain.CurrentDomain.BaseDirectory + "res\\" + meetingId.ToString() + "\\" + file.FileMd5 + "\\" + file.FileName;

                if (JudgeComplete(file))
                {
                    complete(file.FilePath);
                    return;
                }

                if (SelectFileInfo != null && file.FileId == SelectFileInfo.FileId)
                {
                    ProgressCallback2 = progressCallback;
                    Complete = complete;
                    return;
                }

                cancelToken();

                var list = _DownloadFileQueue.ToList();
                _DownloadFileQueue = new System.Collections.Concurrent.ConcurrentQueue<Entity.FileInfo>();
                _DownloadFileQueue.Enqueue(file);
                if (SelectFileInfo != null) _DownloadFileQueue.Enqueue(SelectFileInfo);
                foreach (var item in list)
                {
                    _DownloadFileQueue.Enqueue(item);
                }

                ProgressCallback2 = progressCallback;
                Complete = complete;

                StartDownload();
            }
            catch (Exception ex)
            {
                ex = Utility.Common.GetInnerException(ex);
                file.Message = ex.Message;
                Utility.LoggerProxy.Exception("UploadDownload", "DownloadFile", ex);
                complete(string.Empty);
                cancelToken();
                if (Complete == null) Application.Current.Dispatcher.Invoke(() => MessageBox.Show(ex.Message));
                Complete = null;
                ProgressCallback2 = null;
            }
        }

        static void DownloadFile(Guid meetingId, Entity.FileInfo file, string serviceUrl, Action<ProgressStatus> progressCallback, Action<string> complete)
        {
            if (string.IsNullOrEmpty(file.FileMd5))
            {
                complete(string.Empty);
                return;
            }

            string dir = AppDomain.CurrentDomain.BaseDirectory + "res\\" + meetingId.ToString() + "\\" + file.FileMd5 + "\\";
            string filePath = dir + file.FileName;
            file.FilePath = filePath;

            if (JudgeComplete(file))
            {
                complete(filePath);
                return;
            }

            var slices = RequestService.GetMeetingSubjectFileSlices(file.FileId);

            try
            {
                long downBytesed = 0;
                Stopwatch stopwatch = Stopwatch.StartNew();
                long elapsed = 0;

                if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);

                List<string> filePath2List = new List<string>();
                foreach (var slice in slices)
                {
                    string filePath2 = slices.Count == 1 ? filePath : dir + Utility.Common.ComputeStringMd5(slice.FileUrl) + file.FileType;
                    if (slices.Count > 1) filePath2List.Add(filePath2);

                    long existingLength = 0;

                    if (File.Exists(filePath2))
                    {
                        existingLength = new System.IO.FileInfo(filePath2).Length;
                        if (existingLength == slice.FileSize)
                        {
                            downBytesed += slice.FileSize;
                            continue;
                        }
                        else
                        {
                            downBytesed += existingLength;
                        }
                    }

                    string fileUrl = slice.FileUrl;
                    if (fileUrl.StartsWith("/")) fileUrl = serviceUrl + fileUrl;

                    DownloadFileAsync(fileUrl, filePath2, existingLength, (downBytes) =>
                    {
                        long totalDownBytes = downBytesed + downBytes;

                        if (stopwatch.ElapsedMilliseconds - elapsed < 200) return;
                        elapsed = stopwatch.ElapsedMilliseconds;

                        int percentage = totalDownBytes >= file.FileSize ? 100 : Convert.ToInt32(Math.Floor((decimal)totalDownBytes * 100 / (decimal)file.FileSize));

                        if (percentage < 100) progressCallback(new ProgressStatus(totalDownBytes, file.FileSize, percentage));
                    }).Wait();

                    if (CancellationTokenSource == null)
                    {
                        break;
                    }

                    downBytesed += slice.FileSize;
                }
                stopwatch.Stop();

                if (slices.Count > 1 && CancellationTokenSource != null)
                {
                    using (FileStream output = File.Create(filePath))
                    {
                        foreach (string chunkFile in filePath2List)
                        {
                            using (FileStream input = File.OpenRead(chunkFile))
                            {
                                input.CopyTo(output);
                            }

                            //Task.Run(() =>
                            //{
                            //    try
                            //    {
                            //        Thread.Sleep(200);
                            //        System.IO.File.Delete(chunkFile);
                            //    }
                            //    catch
                            //    {

                            //    }
                            //});
                        }
                    }
                }

                file.FilePath = filePath;
                if (JudgeComplete(file))
                {
                    complete(filePath);
                    progressCallback(new ProgressStatus(file.FileSize, file.FileSize, 100));
                }
                else complete(string.Empty);
            }
            catch (Exception ex)
            {
                ex = Utility.Common.GetInnerException(ex);
                file.Message = ex.Message;
                Utility.LoggerProxy.Exception("UploadDownload", "DownloadFile", ex);
                complete(string.Empty);
                cancelToken();
                if (Complete == null) Application.Current.Dispatcher.Invoke(() => MessageBox.Show(ex.Message));
            }
        }

        public static async Task DownloadFileAsync(string url, string filePath, long existingLength, Action<long> progress)
        {
            using (HttpClient client = new HttpClient())
            {
                using (var request = new HttpRequestMessage(HttpMethod.Get, url))
                {
                    request.Headers.Range = new RangeHeaderValue(existingLength, null);

                    if (CancellationTokenSource != null)
                    {
                        using (HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, CancellationTokenSource.Token))
                        {
                            if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
                            {
                                throw new Exception("文件地址不存在，请重新上传！");
                            }

                            using (Stream contentStream = await response.Content.ReadAsStreamAsync())
                            {
                                FileStream fileStream = null;
                                int count = 0;
                                do
                                {
                                    try
                                    {
                                        fileStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.None, 8192, true);
                                    }
                                    catch
                                    {
                                        Thread.Sleep(1000);
                                        count++;
                                    }
                                } while (fileStream == null && count < 10);

                                if (fileStream != null)
                                {

                                    byte[] buffer = new byte[8192];
                                    long totalRead = 0;
                                    int read;

                                    while (CancellationTokenSource != null && (read = await contentStream.ReadAsync(buffer, 0, buffer.Length, CancellationTokenSource.Token)) > 0)
                                    {
                                        fileStream.Write(buffer, 0, read);
                                        totalRead += read;
                                        progress(totalRead);
                                    }

                                    fileStream.Close();
                                    fileStream.Dispose();
                                    fileStream = null;
                                }
                            }
                        }
                    }
                }
            }
        }

        public long GetElapsedMilliseconds()
        {
            return _Stopwatch.ElapsedMilliseconds;
        }

        public void CancelUpload()
        {
            if (IsUploading)
            {
                IsUploading = false;
                if (_CancellationTokenSource != null)
                {
                    _CancellationTokenSource.Cancel();
                    _CancellationTokenSource.Dispose();
                    _CancellationTokenSource = null;
                }
                SelectFileInfo = null;
            }
        }

        public Action<long, long, int, Entity.UploadFile> ProgressCallback { get; set; }

        void UploadFile(Guid meetingId, Entity.UploadFile file)
        {
            Stopwatch stopwatch2 = Stopwatch.StartNew();
            try
            {
                var fileSliceStreamList = GetFileSliceStreamList(file.FilePath);
                int index = 0;
                string sliceIds = string.Empty;

                foreach (var item in fileSliceStreamList)
                {
                    long sized = ChunkSize * index;

                    var sliceId = Utility.Common.ComputeStreamMd5(item);
                    string dirName = meetingId.ToString()+"/" + sliceId;
                    sliceIds += sliceId;

                    index++;
                    string name =  index.ToString() + "_" + file.FileName;
                    string urlName = name;
                    DateTime startTime = DateTime.Now;
                    Stopwatch stopwatch = Stopwatch.StartNew();
                    long elapsed = 0;

                    UploadResponse res = null;

                    if (RequestService.JudgeExists(dirName, urlName))
                    {
                        var bytesSent = item.Length;
                        bytesSent += sized;

                        var percentage = bytesSent >= file.FileSize ? 100 : Convert.ToInt32(Math.Floor((decimal)bytesSent * 100 / (decimal)file.FileSize));
                        if (ProgressCallback != null) ProgressCallback(bytesSent, stopwatch2.ElapsedMilliseconds, percentage, file);

                        res = new UploadResponse();
                        res.FileSize = Convert.ToInt32(item.Length);
                        res.FileUrl = $"/res/{dirName}/{urlName}";
                    }
                    else
                    {
                        res = RequestService.UploadFile(name, dirName, urlName, _CancellationTokenSource.Token, item, (bytesSent, totalBytes, percentage) =>
                        {
                            bytesSent += sized;

                            if (percentage != 100)
                            {
                                if (stopwatch.ElapsedMilliseconds - elapsed < 200) return;
                                elapsed = stopwatch.ElapsedMilliseconds;
                            }
                            percentage = bytesSent >= file.FileSize ? 100 : Convert.ToInt32(Math.Floor((decimal)bytesSent * 100 / (decimal)file.FileSize));
                            if (ProgressCallback != null) ProgressCallback(bytesSent, stopwatch2.ElapsedMilliseconds, percentage, file);
                        });
                    }
                    stopwatch.Stop();

                    if (string.IsNullOrEmpty(res.Message))
                    {
                        var slice = new Entity.MeetingSubjectFileSlice();
                        slice.SliceId = Guid.NewGuid();
                        slice.FileId = file.FileId;
                        slice.FileUrl = res.FileUrl;
                        slice.FileSize = res.FileSize;
                        slice.DisplayIndex = index;
                        slice.MeetingId = meetingId;
                        slice.StartDate = startTime.ToString("yyyy-MM-dd HH:mm:ss");
                        slice.EndDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                        slice.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds;
                        var res2 = RequestService.InsertMeetingSubjectFileSlice(slice);
                        res.Message = res2.Message;
                    }

                    if (!string.IsNullOrEmpty(res.Message))
                    {
                        file.Message = res.Message;
                        ProgressCallback(0, stopwatch2.ElapsedMilliseconds, 0, file);
                        break;
                    }
                }
                stopwatch2.Stop();
                file.IsComplete = true;
                file.FileMd5 = Utility.Common.ComputeStringMd5(sliceIds);
                file.MeetingId= meetingId;

                if (fileSliceStreamList.Count() > 1)
                {
                    var res= RequestService.SliceToFile(file);
                    if (!string.IsNullOrEmpty(res.Message))
                    {
                        throw new Exception(res.Message);
                    }
                }
              
                Task.Run(() =>
                {
                    try
                    {
                        string dir = "res\\" + meetingId.ToString() + "\\" + file.FileMd5;
                        Utility.Common.CopyFile(file.FilePath, file.FileName, dir);
                    }
                    catch
                    {

                    }
                });
            }
            catch (Exception ex)
            {
                ex = Utility.Common.GetInnerException(ex);
                file.Message = ex.Message;
                stopwatch2.Stop();
                Utility.LoggerProxy.Exception("UploadDownload", "UploadFile2", ex);
                ProgressCallback(0, stopwatch2.ElapsedMilliseconds, 0, file);
            }
        }

        IEnumerable<Stream> GetFileSliceStreamList(string filePath)
        {
            using (var fileStream = System.IO.File.OpenRead(filePath))
            {
                byte[] buffer = new byte[ChunkSize];
                int bytesRead;
                int chunkIndex = 0;

                while ((bytesRead = fileStream.Read(buffer, 0, ChunkSize)) > 0)
                {
                    var memoryStream = new MemoryStream(buffer, 0, bytesRead);
                    memoryStream.Position = 0; 
                    yield return memoryStream;

                    chunkIndex++;
                    buffer = new byte[ChunkSize];
                }
            }
        }
    }


    public class ProgressStatus
    {
        public long SentBytes { get; set; }
        public long TotalBytes { get; set; }
        public int Percentage { get; set; }

        public ProgressStatus(long s, long t, int p)
        {
            SentBytes = s;
            TotalBytes = t;
            Percentage = p;
        }
    }
}
